using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml;
using KnessetCore.Members;
using KnessetCore.Properties;
using KnessetCore.Utils;
using log4net;
using Raccoom.Xml;

namespace KnessetCore.Parser
{
	/// <summary>
	/// Represents a single law parsed from text.
	/// </summary>
	public struct ParsedLaw
	{
		/// <summary>
		/// Who is standing behind this law.
		/// </summary>
		public KnessetMember Member;
		/// <summary>
		/// Law's description.
		/// </summary>
		public String LawDescription;
		/// <summary>
		/// Special law id.
		/// </summary>
		public uint LawID;
		/// <summary>
		/// Link to a document descrbing the full law.
		/// </summary>
		public String LawLink;
		/// <summary>
		/// The knesset number that this law was legislated in. Each law id can appear several times, each
		/// time in a different knesset.
		/// </summary>
		public uint KnessetNumber;
		/// <summary>
		/// A copy constructor.
		/// </summary>
		/// <param name="copy"></param>
		public ParsedLaw(ParsedLaw copy)
		{
			Member = copy.Member;
			LawDescription = copy.LawDescription;
			LawID = copy.LawID;
			LawLink = copy.LawLink;
			KnessetNumber = copy.KnessetNumber;
		}


		///<summary>
		///Returns the fully qualified type name of this instance.
		///</summary>
		///
		///<returns>
		///A <see cref="T:System.String"></see> containing a fully qualified type name.
		///</returns>
		///<filterpriority>2</filterpriority>
		public override string ToString()
		{
			return string.Format("Knesset number: {0} Law id: {1}", KnessetNumber, LawID);
		}
	}

	public class ParsePrivateLawRss : IParser<String, ICollection<ParsedLaw>>
	{
		#region Private members
		/// <summary>
		/// A base address for a search engine that gives you the ability to search for laws.
		/// </summary>
		private readonly string baseLawsearchAddress;
		/// <summary>
		/// Captures law title and number.
		/// </summary>
		private readonly Regex lawTitle = new Regex(
			@"(?<lawID>\d+)" +  // Capture law number
			@"\s*-\s*(?<lawDesc>.*?)$" // Capture description
			, RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
		// Capture member's name who legislated a specific law.
		private readonly Regex lawLegislators = new Regex(
			@"<tr\s*vAlign=Top>.*?" +  // Capture law as part of a table.
			"<td\\s*class=\"LawTextC\">(?<knessetID>\\d+).*?" + // Capture knesset id.
			@"<font.*?[^r]>(?<names>.*?)</Font>" + // Capture members names.
			@".*?</tr>" // end table.
			, RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
		/// <summary>
		/// Used in order to log data.
		/// </summary>
		private readonly ILog logger = LogManager.GetLogger("KnessetCore.Parser");
		#endregion

		#region Constrctors
		/// <summary>
		/// A constructor.
		/// </summary>
		/// <param name="baseLawsearchAddress">A base address for a search engine that gives you the ability to search for laws.</param>
		public ParsePrivateLawRss(String baseLawsearchAddress)
		{
			if (baseLawsearchAddress == null) throw new ArgumentNullException("baseLawsearchAddress");
			this.baseLawsearchAddress = baseLawsearchAddress;
		}

		#endregion

		#region IParser<string,ICollection<ParsedLaw>> Members

		/// <summary>
		/// Parses data.
		/// </summary>
		/// <param name="data">Data to parse. Can not be null.</param>
		/// <returns>Parsed data.</returns>
		/// <exception cref="ParseException">Error while parsing.</exception>
		public ICollection<ParsedLaw> Parse(string data)
		{
			logger.Debug("~~~~~Entered ParsePrivateLawRss.Parse");
			if (data == null) throw new ArgumentNullException("data is null.");
			ICollection<ParsedLaw> returnValue = new List<ParsedLaw>();
			// General workflow:
			// 1. Read rss feed describing the last laws legislated in the knesset.
			// 2. The rss feed does not contain all the appropriate information we need: knesset member name who legislated it.
			// 3. Go to the search page and extract for each law who are the knesset members who are standing
			//	behind it.
			
			// The string is an rss feed read from Knesset site.
			// I want to parse it in order to retrieve the appropriate information.
			try 
			{	        
				XmlTextReader reader = new XmlTextReader(new StringReader(data));
				RssChannel myRssChannel = new RssChannel(reader);
				logger.Info(string.Format("Found {0} to parse.", myRssChannel.Items));
				// Read each item's title.
				foreach (RssItem item in myRssChannel.Items)
				{
					ParsedLaw parsedLaw = new ParsedLaw();
					// Extract law id and description.
					String title = item.Title;
					logger.Info(string.Format("Title: {0}", title));
					Match m = lawTitle.Match(title);
	
					try
					{
						parsedLaw.LawID = Convert.ToUInt32(m.Groups["lawID"].ToString());
					}
					catch (Exception e)
					{
						logger.Error(string.Format("Error while parsing law id: {0} .Continue to the next law.", e.Message));
						Constants.CriticalErrorLogger.Error(string.Format("Error while parsing law id: {0} .Continue to the next law.", e.Message));
						// Ignore this value and continue to the next one.
						break;
					}
					// Next read description.
					parsedLaw.LawDescription = m.Groups["lawDesc"].ToString();
					// Next read law's link.
					parsedLaw.LawLink = item.Link;
					// The knesset number is as defined in the configuration.
					parsedLaw.KnessetNumber = uint.Parse(Settings.Default.KnessetNumber);
					// Need to find the members that legislated this law.
					ICollection<KnessetMember> members = findLegislatingMember(parsedLaw.LawID);
					foreach (KnessetMember member in members)
					{
						ParsedLaw tmpParsedLaw = new ParsedLaw(parsedLaw);
						tmpParsedLaw.Member = member;
						returnValue.Add(tmpParsedLaw);
					}
				}
	
			}
			catch (Exception e)
			{
				logger.Error(e.Message);
				Constants.CriticalErrorLogger.Error(e.Message);
				throw new ParseException(e.Message, e);
			}
			logger.Debug("~~~~~Exited ParsePrivateLawRss.Parse");
			return returnValue;
		}

		#endregion

		#region Helper methods
		/// <summary>
		/// Find the legislating knesset member of a specific law number.
		/// </summary>
		/// <param name="lawID">The lgislated law.</param>
		/// <returns>The knesset members who legislated the law. Can return an empty list.</returns>
		private ICollection<KnessetMember> findLegislatingMember(uint lawID)
		{
			logger.Debug("~~~~~Entered ParsePrivateLawRss.findLegislatingMember");

			#region Variables
			// Holds text read from html page.
			string text;
			ICollection<KnessetMember> returnValue = new List<KnessetMember>();
			KnessetMembers members = KnessetMembers.GetInstance(); 
			#endregion

			try
			{
				#region Run law search engine
				// Create an address to search for the law.
				String address = baseLawsearchAddress + lawID;
				logger.Info(string.Format("Searching for law: {0}", address));
				text = Helpers.GetTextFromURL(address);
				logger.Debug(string.Format("Fetched the following data: {0}", text)); 
				#endregion

				// Search for the specific legislator.
				MatchCollection matches = lawLegislators.Matches(text);
				// I am searching for the current knesset id.
				bool foundKnessetID = false;
				foreach (Match match in matches)
				{
					String knessetID = match.Groups["knessetID"].ToString();
					if (knessetID == Settings.Default.KnessetNumber)
					{
						#region Law found with correct knesset id
						// We found it.
						foundKnessetID = true;
						logger.Debug(string.Format("Found knessetID = {0}", knessetID));
						String names = match.Groups["names"].ToString();
						// Split according to <br>
						String[] seperator = { "<br>" };
						String[] singleNames = names.Split(seperator, StringSplitOptions.RemoveEmptyEntries);
						// Clean each name by removing spaces and &nbsp;
						foreach (string s in singleNames)
						{
							String name = s.Trim().Replace("&nbsp;", " ");
							// Retreive member.
							if (members.IsExist(name))
							{
								returnValue.Add(members.GetByName(name));
							}
							// Something is wrong here.
							else
							{
								logger.Error(String.Format("Knesset member {0} is not found ", name));
								Constants.CriticalErrorLogger.Error(String.Format("Knesset member {0} is not found ", name));
							}
						} 
						#endregion
					}
				}

				if (!foundKnessetID)
				{
					logger.Error(string.Format("Did not find knesset ID: {0}", Settings.Default.KnessetNumber));
					Constants.CriticalErrorLogger.Error(string.Format("Did not find knesset ID: {0}", Settings.Default.KnessetNumber));
				}
			}
			catch (Exception e)
			{
				logger.Error(e.Message);
				Constants.CriticalErrorLogger.Error(e.Message);
				throw new ParseException(e.Message, e);
			}
			logger.Debug("~~~~~Exited ParsePrivateLawRss.findLegislatingMember");

			return returnValue;
		}
		#endregion
	}
}